﻿using System;
using System.Collections.Generic;
using Properties;

namespace Borg
{
    public class TaskQueue
    {
        private readonly Queue<ITask> _queue = new Queue<ITask>();
        private readonly Stack<InvokeMethodInfo> _invokeOnEmptiedStack = new Stack<InvokeMethodInfo>();
        private bool _isExecuteTasksRunning;


        public event CancelTaskEventHandler TaskEnqueueing;
        public event TaskEventHandler TaskEnqueued;
        public event TaskEventHandler TaskDequeued;
        public event TaskEventHandler QueueEmptied;

        public bool IsBusy
        {
            get { return _isExecuteTasksRunning || Count > 0; }
        }

        public int Count
        {
            get { return _queue.Count; }
        }

        public IEnumerable<ITask> QueuedTasks
        {
            get
            {
                foreach (var task in _queue)
                    yield return task;
            }
        }

        public ITask NextTask
        {
            get { return _queue.Count == 0 ? null : _queue.Peek(); }
        }

        internal void Enqueue(ITask task)
        {
            var args = new CancelTaskEventArgs(task, task.Initiator);
            OnEnqueueing(this, args);

            if (args.Cancel)
                Runtime.OnLogEvent(
                    this,
                    new LogEventArgs(
                        MessageType.Info,
                        VerbosityLevel.Normal,
                        Messages.EnqueueingTaskCanceled,
                        new object[]
                            {
                                task.ToString(),
                                task.Initiator.GetName().Name
                            }));
            else
            {
                InternalEnqueue(task);
                ContinueExecuting();
            }
        }

        internal void Enqueue(IEnumerable<ITask> tasks)
        {
            foreach (var task in tasks)
            {
                var args = new CancelTaskEventArgs(task, task.Initiator);
                OnEnqueueing(this, args);

                if (args.Cancel)
                    Runtime.OnLogEvent(
                        this,
                        new LogEventArgs(
                            MessageType.Info,
                            VerbosityLevel.Normal,
                            Messages.EnqueueingTaskCanceled,
                            new object[]
                                {
                                    task.ToString(),
                                    task.Initiator.GetName().Name
                                }));
                else
                    InternalEnqueue(task);
            }

            ContinueExecuting();
        }

        private void InternalEnqueue(ITask task)
        {
            _queue.Enqueue(task);

            if (TaskEnqueued == null)
                return;

            Runtime.InvokeEventHandler(
                "TaskEnqueued",
                TaskEnqueued,
                this,
                new TaskEventArgs(task, task.Initiator));
        }

        public void InvokeOnEmptied(Delegate method, params object[] args)
        {
            InvokeOnEmptied(new InvokeMethodInfo(method, args));
        }

        public void InvokeOnEmptied(NoParameterHandler method)
        {
            InvokeOnEmptied(new InvokeMethodInfo(method, new object[0]));
        }

        public void InvokeOnEmptied(ObjectParameterHandler method, object args)
        {
            InvokeOnEmptied(new InvokeMethodInfo(method, new[] { args }));
        }

        private void InvokeOnEmptied(InvokeMethodInfo methodInfo)
        {
            if (Count > 0)
                _invokeOnEmptiedStack.Push(methodInfo);
            else
                methodInfo.InvokeMethod();
        }

        private void ContinueExecuting()
        {
            if (_isExecuteTasksRunning || _queue.Count == 0)
                return;

            ExecuteTasks();

            while (_invokeOnEmptiedStack.Count > 0 && _queue.Count == 0)
                _invokeOnEmptiedStack.Pop().InvokeMethod();
        }

        private void ExecuteTasks()
        {
            _isExecuteTasksRunning = true;
            ITask task;

            do
            {
                task = _queue.Peek();
                task.Execute();
                _queue.Dequeue();

                if (TaskDequeued != null)
                    Runtime.InvokeEventHandler(
                        "TaskDequeued",
                        TaskDequeued,
                        this,
                        new TaskEventArgs(task, task.Initiator));
            }
            while (_queue.Count > 0 && !Runtime.IsFinalizing);

            if (QueueEmptied != null && _queue.Count == 0)
                Runtime.InvokeEventHandler(
                    "QueueEmptied",
                    QueueEmptied,
                    this,
                    new TaskEventArgs(task, task.Initiator));

            _isExecuteTasksRunning = false;
        }

        private void OnEnqueueing(object sender, CancelTaskEventArgs args)
        {
            if (TaskEnqueueing == null)
                return;

            TaskEnqueueing(sender, args);
        }
    }
}

